#include "surbasic/FileDownloaderEnc.h"
#include "surbasic/BenchmarkBlock.h"
#include "surbasic/surbasic.h"

namespace SDBasic {
namespace download {

FileDownloaderEnc::FileDownloaderEnc(const uint64_t &key,
                                     FileDownloadCallback cb)
    : cb_(cb) {
  AccessToken token;
  TokenManager::get().GetToken(key, token);
  ecies_ = EciesHelper::CreateFromPrivateKeyReturnedByServer(token.privatekey);
}
FileDownloaderEnc::~FileDownloaderEnc() {
  if (ecies_ != NULL) {
    delete ecies_;
  }
}
void FileDownloaderEnc::SendProgressMsg(DownloadProgressInfo::MsgType type) {
  proginfo_.type = type;
  if (cb_.func) cb_.func(proginfo_, cb_.userdata);
}
std::string FileDownloaderEnc::GenUniqueTmpFile(const std::string &origpath) {
  std::string tmpfile = origpath + ".sdown";
  FileStatus st = FileInfo::StatFile(tmpfile);
  if (st.type == FileInfo::NotExisted || st.type == FileInfo::Error)
    return tmpfile;

  for (int i = 1;; ++i) {
    tmpfile = origpath + "." + NumFmt("(%d)", i).str() + ".sdown";
    FileStatus st = FileInfo::StatFile(tmpfile);
    if (st.type == FileInfo::NotExisted || st.type == FileInfo::Error)
      return tmpfile;
  }
}
SD_CODE FileDownloaderEnc::Download(const uint64_t &key, const std::string &id,
                                    const std::string &path) {

  char buf[256];
  snprintf(buf, sizeof(buf), "download file content: %s", id.c_str());
  BenchmarkBlock download_block(buf, 1);

  snprintf(buf, sizeof(buf), "decrypt file content: %s", id.c_str());
  BenchmarkBlock decrypt_block(buf, 1);

  ByteArray storagekey;
  request_.Init();
  proginfo_.Reset();

  const std::string infoid = Digestor::Md5str(id + path);
  int offset = 0;
  proginfo_.size = 0;
  proginfo_.offset = offset;
  SendProgressMsg(DownloadProgressInfo::FILE_DOWN_START);
  std::string tmpfile = GenUniqueTmpFile(path);
  File fp;
  bool blret = fp.OpenForWrite(tmpfile);
  if (!blret) {
    log_callback(LogError, "OpenForWrite() failed: %s", tmpfile.c_str());
    FileInfo::Remove(tmpfile.c_str());
    return SD_LOCAL_FILE_ERROR;
  }
  // blret = fp.SeekTo(File::FROM_END,offset);
  std::string personal_key;
  std::string etag;
  std::string plain_file_path;
  AccessToken token;
  TokenManager::get().GetToken(key, token);

  download_block.Start();

  SD_CODE ret =
      request_.Enc_DownloadFile(token, id, etag, offset, &fp, personal_key);
  fp.Close();
  log_callback(LogDebug, "\r\nEenc_key:%s\r\n", personal_key.c_str());
  if (ret == SD_OPENAPI_SERVER_FILE_CHANGED) {
    log_callback(LogInfo,
                 "[%s] server file changed, we must re-download this file",
                 path.c_str());
    FileInfo::Remove(tmpfile);
    return SD_FAILED;
  }

  FileStatus st = FileInfo::StatFile(tmpfile);
  if (st.type != FileInfo::Regular) {
    log_callback(LogInfo, "file downloaded, but we can't find it any more: %s",
                 tmpfile.c_str());
    FileInfo::Remove(tmpfile);
    return SD_FAILED;
  }
  if (ret != SD_SUCCESSED) {
    if (st.size == 0) FileInfo::Remove(tmpfile);
    log_callback(LogError, "[%s] failed to download file: %s", path.c_str(),
                 tmpfile.c_str());
    return ret;
  }
  SendProgressMsg(DownloadProgressInfo::FILE_DOWN_DONE);
  // general file
  if (personal_key.empty()) {
    plain_file_path = tmpfile;
    goto decrypt_done;
  }

  download_block.set_size(st.size);
  download_block.Stop();
  decrypt_block.Start();

  // decrypt tmpfile
  blret = ecies_->DecryptStorageKeyFromPersonalKey(personal_key, &storagekey);
  if (!blret) {
    log_callback(LogError,
                 "can not decrypt personal key to storage key, Reason:");
    FileInfo::Remove(tmpfile);
    SendProgressMsg(DownloadProgressInfo::FILE_CHECKSUM_ERROR);
    return SD_FAILED;
  }

  plain_file_path = tmpfile + ".tmp";
  blret = AESHelper::DecAndUncompressFile(storagekey, tmpfile, plain_file_path);
  if (!blret) {
    log_callback(LogError, "failed to decrypt and unzip file, reason: %s",
                 plain_file_path.c_str());
    FileInfo::Remove(tmpfile);
    SendProgressMsg(DownloadProgressInfo::FILE_CHECKSUM_ERROR);
    return SD_FAILED;
  }
  SendProgressMsg(DownloadProgressInfo::FILE_CHECKSUM_START);
decrypt_done:
  SendProgressMsg(DownloadProgressInfo::FILE_CHECKSUM_OK);

  if (FileInfo::Rename(plain_file_path, path) == false) {
    log_callback(LogError, "failed to rename %s to %s", plain_file_path.c_str(),
                 path.c_str());
    FileInfo::Remove(plain_file_path);
    SendProgressMsg(DownloadProgressInfo::FILE_ERROR);
    return SD_LOCAL_FILE_ERROR;
  }
  this->proginfo_.type = DownloadProgressInfo::FILE_DONE;
  SendProgressMsg(DownloadProgressInfo::FILE_DONE);
  // clear resume info
  FileInfo::Remove(tmpfile);

  decrypt_block.set_size_by_path(path);
  decrypt_block.Stop();
  return SD_SUCCESSED;
}

SD_CODE FileDownloaderEnc::DownloadWithoutDecryption(const uint64_t &key,
                                                     const std::string &id,
                                                     const std::string &path,
                                                     std::string *src_md5,
                                                     std::string *aes_key) {

  char buf[256];
  snprintf(buf, sizeof(buf), "download file content: %s", id.c_str());
  BenchmarkBlock download_block(buf, 1);

  snprintf(buf, sizeof(buf), "decrypt file content: %s", id.c_str());
  BenchmarkBlock decrypt_block(buf, 1);

  ByteArray storagekey;
  request_.Init();
  proginfo_.Reset();

  const std::string infoid = Digestor::Md5str(id + path);
  int offset = 0;
  FileStatus orig = FileInfo::StatFile(path);
  if (orig.type == FileType::Regular) {
    offset = orig.size;
  }

  proginfo_.size = 0;
  proginfo_.offset = offset;
  SendProgressMsg(DownloadProgressInfo::FILE_DOWN_START);
  File fp;
  bool blret = fp.OpenForWrite(path);
  if (!blret) {
    log_callback(LogError, "OpenForWrite() failed: %s", path.c_str());
    FileInfo::Remove(path.c_str());
    return SD_LOCAL_FILE_ERROR;
  }
  // blret = fp.SeekTo(File::FROM_END,offset);
  std::string personal_key;
  std::string etag;
  AccessToken token;
  TokenManager::get().GetToken(key, token);

  download_block.Start();

  // set timeout
  request_.SetLowSpeedTimeOut(1, 30);
  log_callback(LogError, "Set download timeout: %d", 30);

  SD_CODE ret =
      request_.Enc_DownloadFile(token, id, etag, offset, &fp, personal_key);
  fp.Close();
  log_callback(LogError, "\r\nEenc_key:%s\r\n", personal_key.c_str());
  if (ret == SD_OPENAPI_SERVER_FILE_CHANGED) {
    log_callback(LogError,
                 "[%s] server file changed, we must re-download this file",
                 path.c_str());
    return SD_FAILED;
  }

  FileStatus st = FileInfo::StatFile(path);
  if (st.type != FileInfo::Regular) {
    log_callback(LogError, "file downloaded, but we can't find it any more: %s",
                 path.c_str());
    return SD_FAILED;
  }
  if (ret != SD_SUCCESSED) {
    if (st.size == 0)
      log_callback(LogError, "failed to download file: %s", path.c_str());
    return ret;
  }
  SendProgressMsg(DownloadProgressInfo::FILE_DOWN_DONE);

  download_block.set_size(st.size);
  download_block.Stop();

  decrypt_block.Start();

  // decrypt tmpfile
  blret = ecies_->DecryptStorageKeyFromPersonalKey(personal_key, &storagekey);
  if (!blret) {
    log_callback(LogError,
                 "can not decrypt personal key to storage key, Reason:");
    SendProgressMsg(DownloadProgressInfo::FILE_CHECKSUM_ERROR);
    return SD_FAILED;
  }

  SendProgressMsg(DownloadProgressInfo::FILE_CHECKSUM_OK);

  this->proginfo_.type = DownloadProgressInfo::FILE_DONE;
  SendProgressMsg(DownloadProgressInfo::FILE_DONE);
  decrypt_block.set_size_by_path(path);
  decrypt_block.Stop();

  *aes_key = storagekey.toHexStr();
  *src_md5 = etag;
  return SD_SUCCESSED;
}
}
}  // namespace
